package com.pad.dpscn.daag.sdk.core.interceptor;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.pad.dpscn.daag.sdk.common.constant.SqlConstants;
import com.pad.dpscn.daag.sdk.common.constant.dbTypeModel;
import com.pad.dpscn.daag.sdk.common.message.MqMessage;
import com.pad.dpscn.daag.sdk.core.annotation.DataMapperIntercept;
import com.pad.dpscn.daag.sdk.core.annotation.DataMapperIntercept.PrimaryKeyField;
import com.pad.dpscn.daag.sdk.core.properties.DaagProperties;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.update.Update;

/**
 * 
 * com.pad.dpscn.daag.sdk.core.interceptor  
 * 
 * Description:  
 * 在mybatis中可被拦截的类型有四种(按照拦截顺序)： Executor: 拦截执行器的方法。 
 * ParameterHandler: 拦截参数的处理。
 * ResultHandler：拦截结果集的处理。 StatementHandler:拦截Sql语法构建的处理。
 *  
 * @author yangdezhao
 * @since 2024年3月11日  
 * @version 1.0
 */
@Intercepts({ 
	@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class })
})
@Component
public class MybatisMapperInterceptor implements Interceptor {

	
	private static final Log log = LogFactory.getLog(MybatisMapperInterceptor.class);

    private ConcurrentLinkedQueue<MqMessage> dataQueue = new ConcurrentLinkedQueue<MqMessage>();  

    private DaagProperties daggProperties;
    
    public MybatisMapperInterceptor() {
		super();
	}

	private static final SimpleDateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public MybatisMapperInterceptor(DaagProperties daggProperties) {
		this.daggProperties = daggProperties;
	}
	/**
	 * 拦截类型StatementHandler
	 */
	@Override
	public Object plugin(Object target) {
		// 注意当前判断的类型是我们intercept方法所拦截的类型
		return Plugin.wrap(target, this);
	}

	// 插件运行的代码，它将代替原有的方法
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		try {
			if (invocation.getTarget() instanceof Executor) {
			    MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
			    if (StringUtils.isEmpty(mappedStatement.getId())) {
			        return invocation.proceed();
			    }
			    String className = mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf("."));
			    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
			    Class<?> mapperClass = contextClassLoader.loadClass(className);
			    DataMapperIntercept mapperClassAnnotation = getAnnotationForMapperClass(mapperClass);
			    if (mapperClassAnnotation == null || !dataInterceptContainSqlCommandType(mapperClassAnnotation, mappedStatement)) {
			        return invocation.proceed();
			    }
			    handleSqlCommandType(mapperClassAnnotation, mappedStatement, invocation);
			}
			
		} catch (Exception e) {
			log.error("intercept error", e);
		}
		return invocation.proceed();
	}
	
	private void handleSqlCommandType(DataMapperIntercept mapperClassAnnotation, MappedStatement mappedStatement,
			Invocation invocation) throws Exception {
		String dbType =  getDbType();
		log.info("dbType is:" + dbType);
	    String completeSQL = obtainCompleteSQL(mappedStatement, invocation,dbType);
	    log.info("complete SQL: {}:"+completeSQL);
	    String whereCondition = getWhere(completeSQL);
		SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
		List<String> primaryKeysNameList = primaryKeysNameList(mapperClassAnnotation) ;
		switch (sqlCommandType) {
			case INSERT:
				whereCondition = getWhereConditionOfInsertByPK(invocation,mapperClassAnnotation); 
				MqMessage mqMessage = buildMqMessage(mapperClassAnnotation, mappedStatement, invocation, whereCondition,primaryKeysNameList,completeSQL);
				dataQueue.offer(mqMessage);
				break;
			case UPDATE:
				MqMessage updateMqMessage = buildMqMessage(mapperClassAnnotation, mappedStatement, invocation,whereCondition,primaryKeysNameList,completeSQL);
				dataQueue.offer(updateMqMessage);
				break;
			case DELETE:
				MqMessage deleteMqMessage = buildMqMessage(mapperClassAnnotation, mappedStatement, invocation,whereCondition,primaryKeysNameList,completeSQL);
				dataQueue.offer(deleteMqMessage);
				break;
			default:
				break;
		}
	}
    private String getWhereConditionOfInsertByPK(Invocation invocation,DataMapperIntercept mapperClassAnnotation) {
    	Object parameter = invocation.getArgs()[1];
		PrimaryKeyField[] pks = mapperClassAnnotation.pk();
		StringBuffer buffer = new StringBuffer(SqlConstants.WHERE_PREFIEX);
		for (DataMapperIntercept.PrimaryKeyField field : pks) {
			String propertyName = field.propertyName();
			String columnName = field.columnName();
			Object value = getFieldValueByName2(propertyName, parameter);
			if (value != null && !StringUtils.isEmpty(value.toString())) {
				buffer.append(" " +SqlConstants.WHERE_AND+  " " + columnName + "='" + value + "'");
			}
		}
		return buffer.toString();
    }
	public ConcurrentLinkedQueue<MqMessage> getDataQueue() {  
        return dataQueue;  
    }  
	
	private List<String> primaryKeysNameList(DataMapperIntercept mapperClassAnnotation) {
		List<String> primaryKeysNameList = new ArrayList<String>();
		PrimaryKeyField[] pks = mapperClassAnnotation.pk();
		for (DataMapperIntercept.PrimaryKeyField field : pks) {
			String columnName = field.columnName();
			primaryKeysNameList.add(columnName);
		}
		return primaryKeysNameList;
	}
	   /**
     * 获取SQL中的where后面的条件
     *
     * @param sql
     * @return
     * @throws JSQLParserException
     */
	public String getWhere(String sql) throws JSQLParserException {
		String whereClause="";
		try {
			// 正则表达式模式，开启不区分大小写匹配
			String pattern = "(?i)(update|delete)\\s+.*?\\s+(?i)where\\s+(.*?)(?=(;|$))";
			// 编译正则表达式
			Pattern r = Pattern.compile(pattern);

			// 循环处理每个SQL语句
			Matcher m = r.matcher(sql);
			if (m.find()) {
				// 提取WHERE条件
				whereClause = m.group(2).trim();
			} else {
				log.info("SQL语句中没有找到WHERE条件: " + sql);
			}
			log.info("where 条件为：" + whereClause);
		} catch (Exception e) {
			log.error("getWhere error", e);
		}

		return whereClause;
	}

    public String parseWhereQueryCondition(String comlpateSQL,String sqlCommandType) {
    	String condition="";
        try {
        	net.sf.jsqlparser.statement.Statement statement = (net.sf.jsqlparser.statement.Statement) CCJSqlParserUtil.parse(comlpateSQL);
			if ("UPDATE".equals(sqlCommandType)) {
				Update update = (Update) statement;
				Expression  expression  = update.getWhere();
				condition = expression.toString();
			}
			if ("DELETE".equals(sqlCommandType)) {
				Delete delete = (Delete) statement;
				Expression  expression  = delete.getWhere();
				condition = expression.toString();
			}
		} catch (JSQLParserException e) {
			e.printStackTrace();
		}
        return condition;
    }
	/**
	 * 构建完整的SQL 携带完整的执行参数
	 * 
	 * @param mappedStatement
	 * @param invocation
	 * @return
	 */
	private String obtainCompleteSQL(MappedStatement mappedStatement, Invocation invocation,String dbType) {
		Object parameter = invocation.getArgs()[1];
		BoundSql boundSql = mappedStatement.getBoundSql(parameter);
		// String[] keys = mappedStatement.getKeyColumns();
		String sql = boundSql.getSql().replaceAll("\n", " ").replaceAll("\t", " ").replaceAll("\\s+", " "); // 获取到SQL
		log.info(sql);

		// 获取节点的配置
		Configuration configuration = mappedStatement.getConfiguration();
		// 获取到最终的sql语句
		String complateSql = getSql(configuration, boundSql,dbType);
		return complateSql;
	}

	/**
	 * 构建消息队列message
	 * 
	 * @param classAnnotation
	 * @param mappedStatement
	 * @param invocation
	 * @return
	 */
	private MqMessage buildMqMessage(DataMapperIntercept classAnnotation,
			MappedStatement mappedStatement, Invocation invocation,String whereQueryCondition,List<String> primaryKeysName,String comlpateSQL) {
		String tableName = classAnnotation.tableName();
		// 当前dao操作类型
		String sqlCommandType = mappedStatement.getSqlCommandType().name();
		Object parameter = invocation.getArgs()[1];

		BoundSql boundSql = mappedStatement.getBoundSql(parameter);
		String sql = boundSql.getSql().replaceAll("\n", " ").replaceAll("\t", " ").replaceAll("\\s+", " "); // 获取到SQL
		log.info(sql);
		MqMessage mqMessage = new MqMessage.Builder()
				.operType(sqlCommandType.toLowerCase())
				.table(tableName)
				.operateSql(sql)
				.paramsOperSql(comlpateSQL)
				.primaryKeysName(primaryKeysName)
				.operationTime(System.currentTimeMillis())
				.whereQueryCondition(whereQueryCondition)
				.builder();
		return mqMessage;
	}

	/**
	 * 判断注解中的value是否包含当前操作类型
	 * 
	 * @param classAnnotation
	 * @param mappedStatement
	 * @return boolean
	 */
	private Boolean dataInterceptContainSqlCommandType(DataMapperIntercept classAnnotation, MappedStatement mappedStatement) {
		boolean containsValue = false;
		if (classAnnotation != null) {
			com.pad.dpscn.daag.sdk.core.annotation.DataMapperIntercept.Operation[] value = classAnnotation.operation();
			log.info("Annotation Operation[]：" + JSON.toJSONString(value));
			log.info("mappedStatement.getId():" + mappedStatement.getId());
			String sqlCommandType = mappedStatement.getSqlCommandType().name();
			containsValue = Arrays.stream(value)
					.anyMatch(op -> op.name().equals(sqlCommandType.toUpperCase()));
		}
		return containsValue;
	}

	/**
	 * 封装了一下sql语句
	 *
	 * @param configuration configuration
	 * @param boundSql      boundSql
	 * @param sqlId         sqlId
	 * @return java.lang.String
	 */
	public static String getSql(Configuration configuration, BoundSql boundSql,String dbType) {
		String sql = showSql(configuration, boundSql,dbType);
		StringBuilder str = new StringBuilder(100);
		str.append(sql);
		return str.toString();
	}

	/**
	 * 进行？的替换
	 *
	 * @param configuration
	 * @param boundSql
	 * @return
	 */
	private static String showSql(Configuration configuration, BoundSql boundSql,String dbType) {
		Object parameterObject = boundSql.getParameterObject(); // 获取参数
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		String sql = boundSql.getSql().replaceAll("[\\s]+", " "); // sql语句中多个空格都用一个空格代替
		if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
			// 获取类型处理器注册器，类型处理器的功能是进行java类型和数据库类型的转换
			TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); 
			// 如果根据parameterObject.getClass(）可以找到对应的类型，则替换
			if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
				sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue2(parameterObject)));

			} else {
				// MetaObject主要是封装了originalObject对象，提供了get和set的方法用于获取和设置originalObject的属性值,
				//主要支持对JavaBean、Collection、Map三种类型对象的操作
				MetaObject metaObject = configuration.newMetaObject(parameterObject);
				for (ParameterMapping parameterMapping : parameterMappings) {
					String propertyName = parameterMapping.getProperty();
					if (metaObject.hasGetter(propertyName)) {
						Object obj = metaObject.getValue(propertyName);
						if((obj instanceof java.sql.Date || obj instanceof java.sql.Timestamp) && dbTypeModel.ORACLE.getModeLowerCase().equals(dbType.toLowerCase())) {
							String dateValue = getParameterValue2(obj);
							String dataFun = "TO_TIMESTAMP("+Matcher.quoteReplacement(dateValue)+",'YYYY-MM-DD HH24:MI:SS.FF')";
							sql = sql.replaceFirst("\\?", dataFun);
						}else {
							sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue2(obj)));
						}
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						Object obj = boundSql.getAdditionalParameter(propertyName); // 该分支是动态sql
						sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue2(obj)));
					} else {
						sql = sql.replaceFirst("\\?", "");
					} // 打印出缺失，提醒该参数缺失并防止错位
				}
			}
		}
		return sql;
	}

	/**
	 * 如果参数是String，则添加单引号， 如果是日期，则转换为时间格式器并加单引号； 对参数是null和不是null的情况作了处理
	 *
	 * @param obj obj
	 * @return java.lang.String
	 */
	public static String getParameterValue2(Object obj) {  
        if (obj == null) {  
            return "";  
        }  
        if (obj instanceof String) {  
            return "'" + obj.toString() + "'";  
        } else if (obj instanceof java.sql.Date) {  
            return "'" + DATETIME_FORMAT.format(obj) + "'";  
        } else if (obj instanceof java.sql.Timestamp) {  
            return "'" + DATETIME_FORMAT.format(obj) + "'";  
        } else {  
            return obj.toString();  
        }  
    }  
	
	/**
	 * 获取mapper上的注解
	 * 
	 * @param mapperClass
	 * @return
	 */
	private DataMapperIntercept getAnnotationForMapperClass(Class<?> mapperClass) {
		return mapperClass.getAnnotation(DataMapperIntercept.class);
	}
	public static String getColumnNameByPropertyNameForMapperClass2(Class<?> clazz, String propertyName) {  
        // 检查类上是否有PrimaryKeyMapping注解  
		DataMapperIntercept dataMapperIntercept = clazz.getAnnotation(DataMapperIntercept.class);  
        if (dataMapperIntercept == null) {  
            throw new RuntimeException("class " + clazz.getName() + " is not annotated with @DataMapperIntercept");  
        }  
  
        // 遍历PrimaryKeyMapping注解中的PrimaryKeyField数组  
        for (DataMapperIntercept.PrimaryKeyField field : dataMapperIntercept.pk()) {  
            // 检查propertyName是否匹配  
            if (field.propertyName().equals(propertyName)) {  
                // 返回对应的columnName  
                return field.columnName();  
            }  
        }  
  
        // 如果没有找到匹配的属性名，返回null或抛出异常  
        return null; // 或者 throw new IllegalArgumentException("No matching column found for property: " + propertyName);  
    }  
	
	private static Object getFieldValueByName2(String fieldName, Object parameter) {
		Object fieldValue = null;
		// 获取parameter对象的类
		Class<?> paramClass = parameter.getClass();

		// 获取paramClass的所有字段，包括私有字段
		Field[] fields = paramClass.getDeclaredFields();

		for (Field field : fields) {
		    // 设置为可访问，以便可以访问私有字段
		    field.setAccessible(true);
		    try {
		    	if(field.getName().equals(fieldName)) {
		    		// 获取字段的值
			        fieldValue = field.get(parameter);
			        // 打印字段名和字段值
			        log.info("字段名: " + field.getName() + ", 字段值: " + fieldValue);
			        break;
		    	}
		        
		     
		    } catch (IllegalAccessException e) {
		        // 处理访问异常
		        e.printStackTrace();
		    }
		}
		return fieldValue;
	}
	
	private String getDbType() {
		return daggProperties.getDbType();
	}
}
